[EXPERMENT] Refactor the GUI into a modular package with dynamic command forms and progress hooks#83
[EXPERMENT] Refactor the GUI into a modular package with dynamic command forms and progress hooks#83RenanGBarreto wants to merge 12 commits into
Conversation
…s | decisions (explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events; discover commands/options dynamically by first importing the mkpfs package and fall back to parsing mkpfs --help if import metadata isn't available"
…cit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events; discover commands/options dynamically by first importing the mkpfs package and fall back to parsing mkpfs --help if import metadata isn't available (TASK_0001)
…explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): keep CustomTkinter/tkinter to minimize scope, preserve current behavior and packaging, and focus on modularization (TASK_0002)
… forms | decisions (explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): discover commands/options dynamically by first importing the mkpfs package and fall back to parsing mkpfs --help if import metadata isn't available; allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events; keep CustomTkinter/tkinter to minimize scope"
…explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): discover commands/options dynamically by first importing the mkpfs package and fall back to parsing mkpfs --help if import metadata isn't available; allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events; keep CustomTkinter/tkinter to minimize scope (TASK_0003)
…sioning, and progress mirroring | decisions (explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events"
…sioning, and progress mirroring | decisions (explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events"
…s mirroring | decisions (explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): allow a small shared API in mkpfs itself for command metadata, default output naming, and progress events (TASK_0004)
…ons (explicit user choices — these OVERRIDE the spec doc wherever they conflict; follow them exactly): keep CustomTkinter/tkinter to minimize scope, preserve current behavior and packaging (TASK_0005)
Code Coverage OverviewLanguages: Python Python / code-coverage/pytestThe overall coverage in the branch is 65%. The coverage in the branch is 75%. Show a code coverage summary of the most impacted files.
Updated |
| result = _convert_discovery_meta(meta) | ||
| if result: | ||
| return result | ||
| except Exception: |
| result = _ensure_argparse_meta() | ||
| if result: | ||
| return result | ||
| except Exception: |
| meta = get_cli_metadata(prefer_import=False) | ||
| if meta.get("commands"): | ||
| result = _convert_discovery_meta(meta) | ||
| except Exception: |
| bytes_processed=len(block), | ||
| timestamp=time.time(), | ||
| ) | ||
| except Exception: |
| bytes_processed=len(chunk), | ||
| timestamp=time.time(), | ||
| ) | ||
| except Exception: |
| bytes_processed=raw_block_len, | ||
| timestamp=time.time(), | ||
| ) | ||
| except Exception: |
| bytes_processed=raw_len, | ||
| timestamp=time.time(), | ||
| ) | ||
| except Exception: |
| bytes_processed=len(chunk), | ||
| timestamp=time.time(), | ||
| ) | ||
| except Exception: |
| bytes_processed=len(raw_chunk), | ||
| timestamp=time.time(), | ||
| ) | ||
| except Exception: |
|
|
||
| # Key for 'pack folder' and 'pack file' subcommands. The root parser uses | ||
| # 'command' and 'pack_command' to mirror argparse's nested subparser dests. | ||
| _PACK_SUBKEY: dict[str, str] = { |
There was a problem hiding this comment.
Pull request overview
This PR updates the GUI to better track the evolving CLI surface by adding runtime command/option discovery, default output naming helpers, and a progress-event plumbing layer that can be mirrored into the GUI. It also introduces a mkpfs.gui package compatibility layer while keeping the original mkpfs/gui.py shim.
Changes:
- Add
mkpfs.discoveryfor CLI discovery (import/help fallback), default output naming, and a global progress-event registry. - Wire progress-event emission from CLI progress (
mkpfs.pbar.Progress) and PFSC compression hotspots (mkpfs.pfs) to support GUI progress mirroring. - Expand GUI implementation in
mkpfs/gui.pyto support dynamic navigation/panels based on discovered argparse metadata, plus addmkpfs/gui/package entrypoints and re-export modules.
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
mkpfs/pfs.py |
Emits global progress events during PFSC compression when a progress callback is present. |
mkpfs/pbar.py |
Emits global progress events from TUI progress updates and status messages. |
mkpfs/gui/widgets.py |
New re-export façade for widget classes from the shim module. |
mkpfs/gui/theme.py |
New re-export façade for theme constants from the shim module. |
mkpfs/gui/panels.py |
New re-export façade for panel classes from the shim module. |
mkpfs/gui/i18n.py |
New re-export façade for translations/helpers from the shim module. |
mkpfs/gui/app.py |
New re-export façade for app entry points from the shim module. |
mkpfs/gui/__main__.py |
Adds python -m mkpfs.gui entrypoint with shim-loading fallback. |
mkpfs/gui/__init__.py |
Adds package initializer that loads the shim and copies exports for compatibility. |
mkpfs/gui.py |
Major GUI updates: argparse metadata introspection, dynamic panels, output prefill, footer version, and progress mirroring hooks. |
mkpfs/discovery.py |
New lightweight module providing CLI discovery, output naming helpers, and global progress registry. |
mkpfs/__init__.py |
Re-exports discovery/progress APIs at package root. |
.pi-tasks/TASK_AUTO_0001.md |
Task tracking documentation for the GUI refresh effort. |
.pi-tasks/TASK_0005.md |
Task tracking documentation for window responsiveness requirements. |
.pi-tasks/TASK_0004.md |
Task tracking documentation for output naming/footer/progress mirroring. |
.pi-tasks/TASK_0003.md |
Task tracking documentation for dynamic command navigation/forms. |
.pi-tasks/TASK_0002.md |
Task tracking documentation for GUI package split. |
.pi-tasks/TASK_0001.md |
Task tracking documentation for shared discovery/progress hooks. |
.pi-tasks/.ignore |
Excludes .pi-tasks/ from fd/ripgrep discovery while keeping them tracked by git. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| self._pages = list[tuple[str, str, type | None, str]](dynamic_pages) | ||
| self._dynamic = True | ||
| self._dynamic_meta: dict[str, list[ArgOption]] = meta |
| # Tier 1: use the discovery module's get_cli_metadata (import or help fallback). | ||
| try: | ||
| from mkpfs.discovery import get_cli_metadata | ||
|
|
||
| meta = get_cli_metadata(prefer_import=True) | ||
| if meta.get("commands"): | ||
| result = _convert_discovery_meta(meta) | ||
| if result: | ||
| return result | ||
| except Exception: | ||
| pass |
| def _on_progress(event: Any) -> None: | ||
| """Mirror a ProgressEvent into this panel's progress bar.""" | ||
| with contextlib.suppress(Exception): | ||
| pct: float = event.done / max(event.total, 1) | ||
| self._progress.set(pct) | ||
| self.after(0, lambda: self._progress.configure(mode="determinate")) | ||
|
|
| # Reset progress bar to zero determinate before each new command. | ||
| self._progress.stop() | ||
| self._progress.configure(mode="determinate") | ||
| self._progress.set(0) |
| # First try import-time metadata when requested and available. | ||
| if prefer_import: | ||
| try: | ||
| from . import cli_metadata as meta | ||
| except Exception: | ||
| meta = None | ||
| if meta: | ||
| return {"source": "import", "commands": meta} | ||
|
|
| """mkpfs.gui package — compatibility layer that defers to the top-level shim. | ||
|
|
||
| We must preserve the original import semantics where importing ``mkpfs.gui`` | ||
| returns the shim module located at ``mkpfs/gui.py`` (so that callers relying | ||
| on Path(__file__) for asset resolution keep working). To achieve that while | ||
| also providing a directory containing the refactored source files for | ||
| packaging/tooling, this package initializer loads the shim from the module | ||
| file and then replaces the package entry in sys.modules with that module | ||
| object. The shim module is given a __path__ pointing at this directory so | ||
| ``mkpfs.gui.<submodule>`` imports (e.g. ``mkpfs.gui.__main__``) continue to | ||
| work and submodules can be imported from the package directory. | ||
| """ |
Refactor the GUI into a modular package with dynamic command forms and progress hooks
🤔 Why?
gui.pywas a single 1,300-line monolith — hard to navigate, extend, or share logic with.pack folderform fields and had no way to discover new commands automatically.🔧 What changed
gui.pyinto amkpfs/gui/package with focused modules:app,widgets,panels,theme,i18n,__main__.mkpfs/discovery.py— a lightweight, import-safe module that exposes CLI metadata (commands, options) and a global progress-event registry. The GUI uses this to build form fields dynamically instead of hard-coding them.mkpfs.__init__API that re-exports discovery helpers so GUI and CLI consumers use the same functions for output naming, path normalization, and progress events.pack folder,pack file,pack exfat,verify,inspect,tree,unpack) and their arguments by introspecting the live argparse parsers — no more hard-coded form layouts.Progressandpfsoperations is emitted as typedProgressEvents and forwarded to any registered handler, so the GUI can show live progress.pbar.pyandpfs.pynow emit progress events through the global registry.default_output_name()for consistent, argparse-friendly default filenames across CLI and GUI.🧪 How to test
python -m mkpfs gui— the window should show all commands with dynamically built forms.--helparguments.pack folderoperation from the GUI — progress should show in the log/progress area.💬 Notes for non-technical readers